/**
 * 
 */
package org.codehaus.groovy.grails.plugins.autodelegator

import java.lang.annotation.Annotation
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.codehaus.groovy.grails.plugins.autodelegator.annotation.Delegate;

/**
 * @author <a href="mailto:franz.see@gmail.com">See, Franz Allan Valencia</a>
 *
 */
public class Delegator {
	
    def static delegatedInvokeMethod = { object, name, args ->
		Object value;
		Class[] parameterClasses = Delegator.deriveClasses(args);
		
		Method declaredMethod = Delegator.getDeclaredMethod(object, name, parameterClasses);
		
		def delegate = getDelegate(object);
		Method declaredMethodOfDelegatee = delegate != null ? Delegator.getDeclaredMethod(delegate, name, parameterClasses) : null;
		
		if(declaredMethod != null) {
	    	value = declaredMethod.invoke(object, args);
	    } else if (declaredMethodOfDelegatee != null){
	    	value = declaredMethodOfDelegatee.invoke(delegate, args);
	    } else if (hasMetaClassMethod(object, name)) {
	    	value = getMetaMethodValue(object, name, args);
	    } else if (delegate != null ){
	    	value = getMetaMethodValue(delegate, name, args);
	    } else {
	    	value = getMetaMethodValue(object, name, args);
	    }
	    
	    return value;
	}
    
    protected static Object getDelegate(Object object){
    	Object delegate = null;
    	
    	Field[] declardFields = object.class.getDeclaredFields();
    	int i = 0;
    	while(delegate == null && i < declardFields.length) {
    		Field declaredField = declardFields[i];
    		
    		org.codehaus.groovy.grails.plugins.autodelegator.annotation.Delegate delegateAnnotation = declaredField.getAnnotation(org.codehaus.groovy.grails.plugins.autodelegator.annotation.Delegate.class);
    		if(delegateAnnotation != null) {
    			if(declaredField.isAccessible() == false){
    				declaredField.setAccessible(true);
    	    	}
    	    	
    			delegate = declaredField.get(object);
    		}
    		
    		i++;
    	}
    	
    	return delegate;
    }
    
    protected static hasMetaClassMethod(Object object, String methodName){
    	return object.metaClass.methods.collect{ it.name }.contains(methodName);
    }
	
	protected static deriveClasses(Object... args) {
		Class[] parameterClasses = new Class[args.length];
		for (int index = 0; index < args.length; index++) {
			parameterClasses[index] = args[index].class;
		}
		return parameterClasses;
	}
	
    private static getMetaMethodValue(Object object, String name, Object[] args) {
    	def metaMethod = object.class.metaClass.getMetaMethod(name,args);
    	def value = metaMethod != null ? metaMethod.invoke(object, args) : null;
	    
	    return value;
	}
	
    protected static getDeclaredMethod(Object object, String name, Class[] parameterClasses) {
		Method declaredMethod = Delegator.getMethodTemplate(object, 'getDeclaredMethod', name, parameterClasses);
	    
	    return declaredMethod;
	}
	
	protected static getMethod(Object object, String name, Class[] parameterClasses){
		Method publicMethod = Delegator.getMethodTemplate(object, 'getMethod', name, parameterClasses);
	    
	    return publicMethod;
	}
	
    private static getMethodTemplate = { object, methodName, name, parameterClasses ->
		Method method = null;
		try {
			method = object.class."$methodName"(name, parameterClasses);
	    } catch(NoSuchMethodException e) {}
	    
	    return method;
	}
}
